#include "codeeditor.h"
#include "globalvar.h"

CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
    showErrorLine = false;

    QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
    QSettings setting(appDataPath + "/Setting.ini", QSettings::IniFormat);

    this->setWordWrapMode(QTextOption::NoWrap);

    lineNumberArea = new LineNumberArea(this);

    connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
    connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea);
    connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
    connect(this, &CodeEditor::textChanged, this, &CodeEditor::highlightCurrentLine);

    updateLineNumberAreaWidth(0);

    //获取字体字号并设置
    QFont editorFont = setting.value("EditorFont", QFont("Consolas")).value<QFont>();
    int fontSize = setting.value("EditorFontSize", 12).toInt();
    if (fontSize < 2) fontSize = 2;
    else if (fontSize > 80) fontSize = 80;
    editorFont.setPointSize(fontSize);

    editorFont.setFamilies(QStringList{editorFont.family(), "Microsoft Yahei"});
    this->setFont(editorFont);

    tabSpaceNumber = setting.value("EditorTabWidth", 4).toInt(); //读取Tab符宽度并设置
    QFontMetrics fm(this->font());
    double fontWidth = fm.horizontalAdvance("a");
    this->setTabStopDistance(fontWidth * tabSpaceNumber);

    this->setCursorWidth(2);

    changeColorTheme();
}

void CodeEditor::setShowErrorLine(bool show)
{
    showErrorLine = show;
}
int CodeEditor::getTabSpaceNumber()
{
    return tabSpaceNumber;
}
void CodeEditor::setTabSpaceNumber(int newNumber)
{
    tabSpaceNumber = newNumber;
}

void CodeEditor::changeColorTheme()
{
    if (highlighter != nullptr) delete highlighter;

    highlighter = new Highlighter(this->document());
    highlightCurrentLine();

    QPalette pal = this->palette();
    pal.setColor(QPalette::Base, GlobalVar::editorBackground[GlobalVar::colorTheme]);
    pal.setColor(QPalette::Text, GlobalVar::editorForeground[GlobalVar::colorTheme]);
    this->setPalette(pal);

    if (GlobalVar::colorTheme == 0)
    {
        this->verticalScrollBar()->setStyleSheet(GlobalVar::lightVScrollBarStyle);
        this->horizontalScrollBar()->setStyleSheet(GlobalVar::lightHScrollBarStyle);
    }
    else
    {
        this->verticalScrollBar()->setStyleSheet(GlobalVar::darkVScrollBarStyle);
        this->horizontalScrollBar()->setStyleSheet(GlobalVar::darkHScrollBarStyle);
    }
}

void CodeEditor::keyPressEvent(QKeyEvent *event)
{
    if (GlobalVar::pairCharsMap.contains(event->key()))
    {
        this->textCursor().insertText(GlobalVar::pairCharsMap.value(event->key()));
        this->moveCursor(QTextCursor::PreviousCharacter);
        return;
    }

    switch (event->key())
    {
    //Qt::Key_BraceLeft 大括号, Key_BracketLeft 中括号, Qt::Key_ParenLeft小括号, Qt::Key_QuoteDbl 双引号
    case Qt::Key_Tab: //Tab键的情况
        if (changeIndent("increase")) return;
        break;
    case Qt::Key_Backtab: //Back Tab键的情况
        if (changeIndent("decrease")) return;
        break;
    case Qt::Key_Enter://Enter key的情况
        if (returnPressedAndIndent()) return;
        break;
    case Qt::Key_Return: //小键盘Enter键的情况
        if (returnPressedAndIndent()) return;
        break;
    }

    //Contral+/，用于被选中多行文本的设置或者取消单行注释
    if (event->key() == Qt::Key_Slash && event->modifiers() == Qt::ControlModifier)
    {
        setComment();
        return;
    }

    QPlainTextEdit::keyPressEvent(event);//继续传递信号到基类，完成相应操作。
}

bool CodeEditor::returnPressedAndIndent()
{
    QString blockStr2 = this->textCursor().block().text();
    static QRegularExpression re2("(^[ \t]+)");
    QRegularExpressionMatch match2 = re2.match(blockStr2);
    if (match2.hasMatch())
    {
        this->textCursor().insertText("\n" + match2.captured(1));
        return true;
    }
    return false;
}

bool CodeEditor::changeIndent(QString direction)
{
    int selectionStart = this->textCursor().selectionStart();
    int selectionEnd = this->textCursor().selectionEnd();

    QTextBlock startBlock = this->document()->findBlock(selectionStart);
    QTextBlock endBlock = this->document()->findBlock(selectionEnd);

    int startBlockNum = startBlock.blockNumber();
    int endBlockNum = endBlock.blockNumber();

    QTextBlock tempBlock;
    if (startBlockNum != endBlockNum)
    {
        QTextCursor textCursor = this->textCursor();
        textCursor.clearSelection();

        textCursor.beginEditBlock();
        for (int i = endBlockNum; i >= startBlockNum; i--)
        {
            tempBlock = this->document()->findBlockByNumber(i);
            if (direction == "increase")
            {
                textCursor.setPosition(tempBlock.position());
                textCursor.insertText("\t");
            }
            else
            {
                QString blockText = tempBlock.text();
                if (blockText.isEmpty())
                    continue;
                if (blockText.front() == '\t' || blockText.front() == ' ')
                {
                    textCursor.setPosition(tempBlock.position());
                    textCursor.deleteChar();
                }
            }
        }
        textCursor.endEditBlock();

        textCursor.setPosition(startBlock.position(), QTextCursor::MoveAnchor);
        textCursor.setPosition(endBlock.position() + endBlock.length() - 1, QTextCursor::KeepAnchor);
        this->setTextCursor(textCursor);

        return true;
    }
    return false;
}

void CodeEditor::setComment()
{
    int selectionStart = this->textCursor().selectionStart();
    int selectionEnd = this->textCursor().selectionEnd();

    QTextBlock startBlock = this->document()->findBlock(selectionStart);
    QTextBlock endBlock = this->document()->findBlock(selectionEnd);

    int startBlockNum = startBlock.blockNumber();
    int endBlockNum = endBlock.blockNumber();

    QTextBlock tempBlock;
    QTextCursor textCursor = this->textCursor();
    bool hasSelection = textCursor.hasSelection();
    textCursor.clearSelection();
    bool needChangeToComment = false;
    for (int i = endBlockNum; i >= startBlockNum; i--)
    {
        tempBlock = this->document()->findBlockByNumber(i);
        QString blockText = tempBlock.text();
        blockText = blockText.trimmed();
        if (blockText.length() == 1 || (blockText.length() > 1 && blockText.mid(0, 2) != "//"))
        {
            needChangeToComment = true;
            break;
        }
    }

    textCursor.beginEditBlock();
    if (needChangeToComment)
    {
        for (int i = endBlockNum; i >= startBlockNum; i--)
        {
            tempBlock = this->document()->findBlockByNumber(i);
            textCursor.setPosition(tempBlock.position());
            textCursor.insertText("//");
        }
    }
    else
    {
        for (int i = endBlockNum; i >= startBlockNum; i--)
        {
            tempBlock = this->document()->findBlockByNumber(i);
            QString blockText = tempBlock.text();
            QString trimmedBlockText = blockText.trimmed();
            if (trimmedBlockText.length() > 1 && trimmedBlockText.mid(0, 2) == "//")
            {
                int indexOfDblSlash = blockText.indexOf("//");
                textCursor.setPosition(tempBlock.position() + indexOfDblSlash);
                textCursor.deleteChar();
                textCursor.deleteChar();
            }
        }
    }
    textCursor.endEditBlock();

    if (hasSelection)
    {
        textCursor.setPosition(startBlock.position(), QTextCursor::MoveAnchor);
        textCursor.setPosition(endBlock.position() + endBlock.length() - 1, QTextCursor::KeepAnchor);
    }
    this->setTextCursor(textCursor);
}

int CodeEditor::lineNumberAreaWidth()
{
    int digits = 1;
    int max = qMax(1, blockCount());
    while (max >= 10)
    {
        max /= 10;
        ++digits;
    }

    int space = 30 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
    return space;
}

void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
    if (dy)
        lineNumberArea->scroll(0, dy);
    else
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());

    if (rect.contains(viewport()->rect()))
        updateLineNumberAreaWidth(0);
}

void CodeEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);

    QRect cr = contentsRect();
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}

void CodeEditor::wheelEvent(QWheelEvent *e)
{
    if (e->modifiers() == Qt::ControlModifier)
    {
        if (e->angleDelta().y() > 0 && this->font().pointSize() < 80)
        {
            this->zoomIn(1);
        }
        else if (e->angleDelta().y() < 0 && this->font().pointSize() > 2)
        {
            this->zoomOut(1);
        }

        QFontMetrics fm(this->font());
        double fontWidth = fm.horizontalAdvance("a");
        this->setTabStopDistance(fontWidth * tabSpaceNumber);

        QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
        QSettings setting(appDataPath + "/Setting.ini", QSettings::IniFormat);
        setting.setValue("EditorFontSize", this->font().pointSize());
    }
    else
    {
        QPlainTextEdit::wheelEvent(e);
    }
}

void CodeEditor::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (!this->isReadOnly())
    {
        QTextEdit::ExtraSelection selection;

        if (showErrorLine)
        {
            showErrorLine = false;
            selection.format.setBackground(GlobalVar::errorLineBackground[GlobalVar::colorTheme]);
            selection.format.setForeground(GlobalVar::errorLineForeground[GlobalVar::colorTheme]);
        }
        else
        {
            selection.format.setBackground(GlobalVar::currentLineBackground[GlobalVar::colorTheme]);
        }

        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = this->textCursor();
        selection.cursor.clearSelection();
        extraSelections.append(selection);
    }

    if (!this->textCursor().hasSelection())
    {
        bool found = false;
        if (this->textCursor().position() > 0)
            found = searchPairBrackets(extraSelections, -1);
        if (!found)
            searchPairBrackets(extraSelections, 0);
    }
    else
    {
        QString selectedText=this->textCursor().selectedText().trimmed();
        QRegularExpression regularExp(QRegularExpression::escape(selectedText), QRegularExpression::CaseInsensitiveOption);

        if (!selectedText.isEmpty()&&regularExp.isValid())
        {   
            QRegularExpressionMatchIterator matchIterator = regularExp.globalMatch(this->toPlainText());

            while (matchIterator.hasNext())
            {
                QRegularExpressionMatch match = matchIterator.next();
                QTextCursor cursor=this->textCursor();
                cursor.setPosition(match.capturedStart(), QTextCursor::MoveAnchor);
                cursor.setPosition(match.capturedEnd(), QTextCursor::KeepAnchor);

                QTextEdit::ExtraSelection selection;
                selection.format.setBackground(GlobalVar::extraSelectionBackground[GlobalVar::colorTheme]);
                selection.cursor = cursor;
                extraSelections.append(selection);
            }
        }
    }

    this->setExtraSelections(extraSelections);
}

bool CodeEditor::searchPairBrackets(QList<QTextEdit::ExtraSelection>& extraSelections, int offset)
{
    QTextCursor textCursor = this->textCursor();
    textCursor.setPosition(textCursor.position() + offset);
    int cursorPos = this->textCursor().position() + offset;
    int newPos = cursorPos;
    QTextDocument *document = this->document();
    QChar newChar = document->characterAt(newPos);
    QChar bracketChar;
    QChar pairChar;
    bool found = false;
    if (GlobalVar::rightBracketMap.contains(newChar) && isOperatorFormat(textCursor))
    {
        bracketStack.clear();
        bracketChar = newChar;
        pairChar = GlobalVar::rightBracketMap.value(bracketChar);
        bracketStack.push(bracketChar);

        while(newPos != 0)
        {
            newPos -= 1;
            newChar = document->characterAt(newPos);
            textCursor.setPosition(newPos);
            if (newChar == bracketChar && isOperatorFormat(textCursor))
                bracketStack.push(bracketChar);
            else if (newChar == pairChar && isOperatorFormat(textCursor))
                bracketStack.pop();

            if (bracketStack.size() == 0)
            {
                found = true;
                break;
            }
        }
    }
    else if (GlobalVar::leftBracketMap.contains(newChar) && isOperatorFormat(textCursor))
    {
        bracketStack.clear();
        bracketChar = newChar;
        pairChar = GlobalVar::leftBracketMap.value(bracketChar);
        bracketStack.push(bracketChar);
        int textCount = this->document()->characterCount();

        while(newPos != textCount - 1)
        {
            newPos += 1;
            newChar = document->characterAt(newPos);
            textCursor.setPosition(newPos);
            if (newChar == bracketChar && isOperatorFormat(textCursor)) bracketStack.push(bracketChar);
            else if (newChar == pairChar && isOperatorFormat(textCursor)) bracketStack.pop();

            if (bracketStack.size() == 0)
            {
                found = true;
                break;
            }
        }
    }

    if (found)
    {
        QTextEdit::ExtraSelection selection;

        QTextCursor textCursor = this->textCursor();

        textCursor.setPosition(cursorPos, QTextCursor::MoveAnchor);
        textCursor.setPosition(cursorPos + 1, QTextCursor::KeepAnchor);

        selection.format.setBackground(GlobalVar::symbolColor[GlobalVar::colorTheme]);
        selection.format.setForeground(this->palette().color(QPalette::Base));
        //selection.format.setProperty(QTextFormat::FullWidthSelection, false);
        selection.cursor = textCursor;
        extraSelections.append(selection);

        textCursor.setPosition(newPos, QTextCursor::MoveAnchor);
        textCursor.setPosition(newPos + 1, QTextCursor::KeepAnchor);
        selection.cursor = textCursor;
        extraSelections.append(selection);
    }

    return found;
}

bool CodeEditor::isOperatorFormat(QTextCursor textCursor)
{
    int pos = textCursor.positionInBlock();
    foreach (QTextLayout::FormatRange r, textCursor.block().layout()->formats())
    {
        if (pos >= r.start && pos < r.start + r.length && r.format == highlighter->getOperatorFormat())
        {
            return true;
            break;
        }
    }
    return false;
}

void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(lineNumberArea);
    painter.setFont(this->font());
    painter.fillRect(event->rect(), GlobalVar::lineNumberAreaColor[GlobalVar::colorTheme]);

    QTextBlock block = this->firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
    int bottom = top + qRound(blockBoundingRect(block).height());

    while (block.isValid() && top <= event->rect().bottom())
    {
        if (block.isVisible() && bottom >= event->rect().top())
        {
            QString number = QString::number(blockNumber + 1);
            painter.setPen(this->palette().color(QPalette::Text));
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignCenter, number);
        }

        block = block.next();
        top = bottom;
        bottom = top + qRound(blockBoundingRect(block).height());
        ++blockNumber;
    }
}
